const { SyncHook } = require('tapable');
const esprima = require('esprima');
const estraverse = require('estraverse');
const escodegen = require('escodegen');
const ejs = require('ejs');
const path = require('path');
const fs = require('fs');
class Compiler {
    constructor(options) {
        this.options = options;
        this.hooks = {
            entryOption: new SyncHook(['config']),
            afterPlugins: new SyncHook(['config']),
            run: new SyncHook(['config']),
            compile: new SyncHook(['config']),
            afterCompile: new SyncHook(['config']),
            emit: new SyncHook(['config']),
            done: new SyncHook(['config'])
        }
        let plugins = options.plugins;
        if (plugins && plugins.length > 0)
            plugins.forEach(plugin => {
                plugin.apply(this);
            });
        //触发插件挂载完成事件
        this.hooks.afterPlugins.call(this);
    }
    //找到入口文件进行编译
    run() {
        this.hooks.run.call(this);
        let { entry,
            output: { path: dist, filename }, resolveLoaders: { modules: loaderPath }, module: { rules } } = this.options;
        let root = process.cwd();//取得当前工作目录 
        let entryPath = path.join(root, entry);//得到入口文件的绝对路径
        let modules = {};//存放着所有的模块 moduleId=> 源代码
        this.hooks.compile.call(this);
        let entryId;
        parseModule(entryPath, true);
        this.hooks.afterCompile.call(this);

        let bundle = ejs.compile(fs.readFileSync(path.join(__dirname, 'main.ejs'), 'utf8'))({
            modules, entryId
        });
        this.hooks.emit.call(this);
        fs.writeFileSync(path.join(dist, filename), bundle);
        this.hooks.done.call(this);

        function parseModule(modulePath, isEntry) {//  ./src/index.js
            //取得入口文件的文件内容
            let source = fs.readFileSync(modulePath, 'utf8');
            for (let i = 0; i < rules.length; i++) {
                let rule = rules[i];
                if (rule.test.test(modulePath)) {
                    let loaders = rule.use || rule.loader;
                    if (loaders instanceof Array) {
                        for (let j = loaders.length - 1; j >= 0; j--) {
                            let loader = loaders[j];//less-loader
                            loader = require(path.join(root, loaderPath, loader));
                            source = loader(source);//less-loader=>css
                        }
                    } else if (typeof loaders == 'string') {

                    } else if (typeof loaders == 'object') {
                        loader = loaders.loader;

                    }
                }
            }
            //root=D:\vipcode\project\usepack modulePath=D:\vipcode\project\usepack/src/index.js
            let parentPath = path.relative(root, modulePath);//    src/index.js
            //TODO 执行loader进行转换
            let result = parse(source, path.dirname(parentPath));//用来解析模块内容并且返回依赖的模块
            modules['./' + parentPath] = result.source;
            if (isEntry) entryId = './' + parentPath;
            let requires = result.requires;
            if (requires && requires.length > 0) {
                requires.forEach(require => parseModule(path.join(root, require)));
            }
        }
        function parse(source, parentPath) {//parentPath 是一个相对的。 。src
            let ast = esprima.parse(source);//生成抽象语法树
            //遍历抽象语树，1.找到此模块依赖的模块 2. 替换掉老的加载路径 
            let requires = [];
            estraverse.replace(ast, {
                enter(node, parent) {
                    if (node.type == 'CallExpression' && node.callee.name == 'require') {
                        let name = node.arguments[0].value;//原模块路径 ./a/a1=> ./src/a/a1.js
                        name += (name.lastIndexOf('.') > 0 ? '' : '.js');
                        let moduleId = './' + path.join(parentPath, name);// src/a/a1=>./src/a/a1.js
                        requires.push(moduleId);
                        node.arguments = [{ type: 'Literal', value: moduleId }];
                        return node;//返回新节点，老节点
                    }
                }
            });
            source = escodegen.generate(ast);
            return { requires, source };
        }



    }
}
module.exports = Compiler;